package com.h2.ref.server.web.social;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.social.connect.Connection;
import org.springframework.social.connect.ConnectionFactory;
import org.springframework.social.connect.ConnectionFactoryLocator;
import org.springframework.social.connect.support.OAuth1ConnectionFactory;
import org.springframework.social.connect.support.OAuth2ConnectionFactory;
import org.springframework.social.connect.web.ConnectSupport;
import org.springframework.social.connect.web.ProviderSignInAttempt;
import org.springframework.social.connect.web.ProviderSignInUtils;
import org.springframework.social.connect.web.SignInAdapter;
import org.springframework.social.facebook.api.Facebook;
import org.springframework.social.support.URIBuilder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.servlet.view.RedirectView;

import com.h2.ref.server.auth.AuthUtils;
import com.h2.ref.server.user.IUserService;
import com.h2.ref.server.user.model.User;
import com.h2.ref.server.web.ViewConstants;

@Controller
public class SocialLogInController {
   
   protected static final Logger LOG =
         LoggerFactory.getLogger(SocialLogInController.class);
   
   ////
   
//   private static final String PROVIDER_ERROR_ATTRIBUTE =
//         "social.provider.error";
   
   ////
   
   private IUserService _userService;

   private ConnectionFactoryLocator _connectionRegistry;
   
   private ConnectionRepositoryInMem _connectionDao;
   
   private final ConnectSupport _webSupport = new ConnectSupport();
   
   ////
   
   private String _signInUrl = ViewConstants.LOGIN_SOCIAL + ".html";
   
   private String _defaultTargetUrl;
   
   ////
   
   @RequestMapping(value = ViewConstants.LOGIN_SOCIAL)
   public String home(Model model) {
      model.addAttribute("providerIds",
            getConnectionRegistry().registeredProviderIds());
      return ViewConstants.LOGIN_SOCIAL_VIEW;
   }
   
   /**
    * Process a sign-in form submission by commencing the process of
    * establishing a connection to the provider on behalf of the user. For
    * OAuth1, fetches a new request token from the provider, temporarily stores
    * it in the session, then redirects the user to the provider's site for
    * authentication authorization. For OAuth2, redirects the user to the
    * provider's site for authentication authorization.
    */
   @RequestMapping(value = ViewConstants.LOGIN_SOCIAL + "/{providerId}",
         method=RequestMethod.POST)
   public RedirectView signIn(@PathVariable String providerId,
         NativeWebRequest request) {
      ConnectionFactory<?> connectionFactory = getConnectionRegistry()
            .getConnectionFactory(providerId);
      try {
         return new RedirectView(_webSupport.buildOAuthUrl(connectionFactory,
               request));
      } catch (Exception exp) {
         return new RedirectView(URIBuilder.fromUri(getSignInUrl())
               .queryParam("error", "provider").build().toString(), true);
      }
   }
   
   /**
    * Process the authentication callback from an OAuth 1 service provider.
    * Called after the member authorizes the authentication, generally done once
    * by having he or she click "Allow" in their web browser at the provider's
    * site. Handles the provider sign-in callback by first determining if a
    * local user account is associated with the connected provider account. If
    * so, signs the local user in by delegating to
    * {@link SignInAdapter#signIn(String, Connection, NativeWebRequest)} If not,
    * redirects the user to a signup page to create a new account with
    * {@link ProviderSignInAttempt} context exposed in the HttpSession.
    * 
    * @see ProviderSignInAttempt
    * @see ProviderSignInUtils
    */
   @RequestMapping(value = ViewConstants.LOGIN_SOCIAL + "/{providerId}",
         method = RequestMethod.GET, params = "oauth_token")
   public String oauth1Callback(@PathVariable String providerId,
         NativeWebRequest request) {
      try {
         OAuth1ConnectionFactory<?> connectionFactory =
               (OAuth1ConnectionFactory<?>) getConnectionRegistry()
                  .getConnectionFactory(providerId);
         Connection<?> connection = _webSupport.completeConnection(
               connectionFactory, request);
         return handleSignIn(connection, request);
      } catch (Exception exp) {
         return ViewConstants.REDIRECT + URIBuilder.fromUri(getSignInUrl())
               .queryParam("error", "provider").build().toString();
      }
   }

   /**
    * Process the authentication callback from an OAuth 2 service provider.
    * Called after the user authorizes the authentication, generally done once
    * by having he or she click "Allow" in their web browser at the provider's
    * site. Handles the provider sign-in callback by first determining if a
    * local user account is associated with the connected provider account. If
    * so, signs the local user in by delegating to
    * {@link SignInAdapter#signIn(String, Connection, NativeWebRequest)}. If
    * not, redirects the user to a signup page to create a new account with
    * {@link ProviderSignInAttempt} context exposed in the HttpSession.
    * 
    * @see ProviderSignInAttempt
    * @see ProviderSignInUtils
    */
   @RequestMapping(value = ViewConstants.LOGIN_SOCIAL + "/{providerId}",
         method = RequestMethod.GET, params = "code")
   public String oauth2Callback(@PathVariable String providerId,
         @RequestParam("code") String code, NativeWebRequest request) {
      try {
         OAuth2ConnectionFactory<?> connectionFactory =
               (OAuth2ConnectionFactory<?>) getConnectionRegistry()
                  .getConnectionFactory(providerId);
         Connection<?> connection = _webSupport.completeConnection(
               connectionFactory, request);
         return handleSignIn(connection, request);
      } catch (Exception exp) {
         LOG.warn("Exception while handling OAuth2 callback ("
               + exp.getMessage() + "). Redirecting to " + getSignInUrl());
         return ViewConstants.REDIRECT + URIBuilder.fromUri(getSignInUrl())
               .queryParam("error", "provider").build().toString();
      }
   }

   /**
    * Process the authentication callback when neither the oauth_token or code
    * parameter is given, likely indicating that the user denied authorization
    * with the provider. Redirects to application's sign in URL, as set in the
    * signInUrl property.
    */
   @RequestMapping(value = ViewConstants.LOGIN_SOCIAL + "/{providerId}",
         method = RequestMethod.GET)
   public String canceledAuthorizationCallback() {
      return ViewConstants.REDIRECT + getSignInUrl();
   }

   private String handleSignIn(Connection<?> connection,
         NativeWebRequest request) {
      Integer userId = getConnectionDao().findUser(connection);
      if (userId == null) {
         request.setAttribute("socialConnection", connection,
               RequestAttributes.SCOPE_SESSION);
         
         if (connection.getApi() instanceof Facebook) {
            @SuppressWarnings("unchecked")
            Connection<Facebook> facebookConnection =
                  (Connection<Facebook>) connection;
            request.setAttribute("socialConnection",
                  facebookConnection,
                  RequestAttributes.SCOPE_SESSION);
            
            request.setAttribute("socialProfile",
                  facebookConnection.getApi().userOperations().getUserProfile(),
                  RequestAttributes.SCOPE_SESSION);
         }
         return ViewConstants.REDIRECT + ViewConstants.REGISTER_SOCIAL + ".html";
      }
      else {
         User user = getUserService().findByUserId(userId);
         AuthUtils.changeAuth(user.getName(), user.getPassword());
         return ViewConstants.REDIRECT + getDefaultTargetUrl();
      }
   }

   /**
    * @return the userService
    */
   public IUserService getUserService() {
      return _userService;
   }

   /**
    * @param userService the userService to set
    */
   public void setUserService(IUserService userService) {
      _userService = userService;
   }

   /**
    * @return the connectionRegistry
    */
   public ConnectionFactoryLocator getConnectionRegistry() {
      return _connectionRegistry;
   }

   /**
    * @param connectionRegistry the connectionRegistry to set
    */
   public void setConnectionRegistry(ConnectionFactoryLocator connectionRegistry) {
      _connectionRegistry = connectionRegistry;
   }

   /**
    * @return the signInUrl
    */
   public String getSignInUrl() {
      return _signInUrl;
   }

   /**
    * @param signInUrl the signInUrl to set
    */
   public void setSignInUrl(String signInUrl) {
      _signInUrl = signInUrl;
   }

   /**
    * @return the connectionDao
    */
   public ConnectionRepositoryInMem getConnectionDao() {
      return _connectionDao;
   }

   /**
    * @param connectionDao the connectionDao to set
    */
   public void setConnectionDao(ConnectionRepositoryInMem connectionDao) {
      _connectionDao = connectionDao;
   }

   /**
    * @return the defaultTargetUrl
    */
   public String getDefaultTargetUrl() {
      return _defaultTargetUrl;
   }

   /**
    * @param defaultTargetUrl the defaultTargetUrl to set
    */
   public void setDefaultTargetUrl(String defaultTargetUrl) {
      _defaultTargetUrl = defaultTargetUrl;
   }

}
